/*
 * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
 * Copyright (C) 2024  Mio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Plugin for Image Conversion using macOS' ImageIO Framework.
 */

#include <CoreFoundation/CoreFoundation.h>
#include <ImageIO/ImageIO.h>

#include "pd/converter.h"

#define GIF_UTTYPE "com.compuserve.gif"

// #define DEBUG

#ifdef DEBUG
#define LOG(...) \
	do { \
		fprintf(stderr, "imageio_converter: "__VA_ARGS__); \
	} while (0);
#else
#define LOG(...)
#endif

static Converter* create_converter(const char* format);

typedef struct IIOConverter IIOConverter;

struct IIOConverter
{
	Converter parent;
	CFStringRef type;
	CFMutableArrayRef sources;
	CFMutableArrayRef sourceDelays;
};

static bool iio_converter_append_frame(Converter* parent, const char *path)
{
	if (!parent)
		return false;

	LOG("appending frame %s\n", path);
	
	IIOConverter* converter = (IIOConverter*)parent;
	
	CFStringRef p = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
	CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, p, kCFURLPOSIXPathStyle, FALSE);
	CGImageSourceRef source = CGImageSourceCreateWithURL(url, NULL);
	
	CFArrayAppendValue(converter->sources, source);
	unsigned zero = 0;
	CFArrayAppendValue(converter->sourceDelays, CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &zero));
	
	CFRelease(url);
	CFRelease(p);
	return false;
}

static void iio_converter_set_frame_delay(Converter* parent, unsigned delay_ms)
{
	if (!parent)
		return;

	float delay_s = delay_ms / 1000.0; // seconds
	
	IIOConverter* converter = (IIOConverter*)parent;
	CFIndex count = CFArrayGetCount(converter->sourceDelays);

	CFNumberRef zeroDelay = (CFNumberRef)CFArrayGetValueAtIndex(converter->sourceDelays, count - 1);
	CFArrayRemoveValueAtIndex(converter->sourceDelays, count - 1); // not sure if this releases for us.
	CFRelease(zeroDelay);

	CFNumberRef delay = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &delay_s);
	
	CFArrayAppendValue(converter->sourceDelays, delay);
}

static bool iio_converter_write(Converter* parent, const char *path)
{
	if (!parent)
		return false;
	
	LOG("writing image to %s\n", path);

	IIOConverter* converter = (IIOConverter*)parent;

	CFStringRef p = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
	CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, p, kCFURLPOSIXPathStyle, FALSE);
	CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, converter->type, 
		CFArrayGetCount(converter->sources), NULL);

	if (CFStringCompare(converter->type, CFSTR(GIF_UTTYPE), 0) == kCFCompareEqualTo)
	{
		LOG("Creating a GIF!\n");

		unsigned loops = 0;      // infinite loop
		
		CFNumberRef cfLoops = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &loops);
		
		CFMutableDictionaryRef options = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, NULL, NULL);
		CFDictionarySetValue(options, kCGImagePropertyGIFLoopCount, cfLoops);
		
		CFMutableDictionaryRef imageProperties = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, NULL, NULL);
		CFDictionaryAddValue(imageProperties, kCGImagePropertyGIFDictionary, options);
		
		CGImageDestinationSetProperties(destination, imageProperties);
		
		CFRelease(options);
		CFRelease(imageProperties);
	}

	CFMutableDictionaryRef frameProperties = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, NULL, NULL);
	CFMutableDictionaryRef properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, NULL, NULL);

	CFIndex count = CFArrayGetCount(converter->sources);
	for (CFIndex i = 0; i < count; ++i)
	{
		// FIXME: GIF specific
		CFDictionarySetValue(frameProperties, kCGImagePropertyGIFUnclampedDelayTime, 
			CFArrayGetValueAtIndex(converter->sourceDelays, i));
		CFDictionarySetValue(properties, kCGImagePropertyGIFDictionary, frameProperties);

		const CGImageSourceRef source = (const CGImageSourceRef)CFArrayGetValueAtIndex(converter->sources, i);
		CGImageDestinationAddImageFromSource(destination, source, 0, properties);
	}
	
	CGImageDestinationFinalize(destination);

	CFRelease(destination);
	CFRelease(url);
	CFRelease(p);

	return true;
}

static void iio_converter_dispose(Converter* parent)
{
	LOG("disposing\n");	
	
	if (!parent)
		return;
	
	IIOConverter* converter = (IIOConverter*)parent;

	// The below two should take care of the contents themselves.
	CFRelease(converter->sources);
	CFRelease(converter->sourceDelays);

	free(converter);
}

bool initialize_converter_library(ConverterInfo* info)
{
	info->name = "ImageIO Converter";
	info->api_version = PD_CONVERTER_API_VERSION;
	info->create_converter = create_converter;

	return true;
}

void deinitialize_converter_library(ConverterInfo* info)
{
	LOG("deinitializing imageio (nothing!)\n");
}

static Converter* create_converter(const char* format)
{
	IIOConverter* converter = NULL;
	
	converter = calloc(1, sizeof *converter);
	
	converter->parent.append_frame = iio_converter_append_frame;
	converter->parent.set_frame_delay = iio_converter_set_frame_delay;
	converter->parent.write = iio_converter_write;
	converter->parent.dispose = iio_converter_dispose;
	
	converter->sources = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
	converter->sourceDelays = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
	
	if (strncmp(format, "GIF", 3) == 0)
	{
		converter->type = CFSTR(GIF_UTTYPE);
	}

	return &converter->parent;
}
